// Copyright (c) 2023 Presto Labs Pte. Ltd.
// Author: tianqiang

#include "coin2/exchange/uniswap/api_order/grpc/grpc_trade_client.h"

#include "coin2/exchange/base/api_base/grpc/auth.h"

namespace coin2::exchange::uniswap::api_order::grpc {

const std::string UniswapGrpcTradeClient::web3_default_internal_grpc_host_ = "10.21.104.58";
const int32_t UniswapGrpcTradeClient::web3_default_internal_grpc_host_port_ = 49400;
const std::string UniswapGrpcTradeClient::web3_default_blockchain_name_ = "arbitrum";

UniswapGrpcTradeClient::UniswapGrpcTradeClient(
    const MarketExchangeApi& mea,
    const NativeClientConfig& config_override,
    bool is_grpc_client) {
  if (!is_grpc_client) {
    return;
  }
  mea_ = mea;
  NativeClientConfig config;
  config.set_grpc_host(web3_default_internal_grpc_host_);
  config.set_grpc_host_port(web3_default_internal_grpc_host_port_);
  config.set_blockchain_name(web3_default_blockchain_name_);
  config.MergeFrom(config_override);
  LOG(INFO) << "[UniswapGrpcTradeClient] config: " << config.DebugString();

  auth_ = std::make_unique<UniswapGrpcAuth>(config.key_filepath());

  std::string target = config.grpc_host() + ":" + std::to_string(config.grpc_host_port());

  std::shared_ptr<::grpc::ChannelCredentials> channel_creds;
  if (config.grpc_disable_ssl()) {
    channel_creds = ::grpc::InsecureChannelCredentials();
  } else {
    channel_creds = ::grpc::SslCredentials(::grpc::SslCredentialsOptions());
    using namespace coin2::exchange::base::api_base::grpc;

    DefaultAuth::Composite(&channel_creds, config.grpc_access_token());
    XunkeMgmtAuth::Composite(&channel_creds, config.grpc_access_token());
  }

  channel_ = ::grpc::CreateChannel(target, channel_creds);
  stub_ = Web3OrderService::NewStub(channel_);
  blockchain_name_ = config.blockchain_name();
}

void UniswapGrpcTradeClient::PlaceOrder(const OrderExecution& execution) {
  using Response = ::coin2::service::QuerySubmitOrderResponseProto;

  auto context = GetGrpcContextWithTimeout(60);

  auto request = GetPlaceOrderRequest(*execution.order_spec);
  auto response = std::make_shared<Response>();

  context->request = request;
  context->response = response;
  context->oc = execution.oc;

  context->query_type = GRPC_PLACE_ORDER;

  stub_->async()->query_submit_order(
      &context->context,
      request.get(),
      response.get(),
      [context, this](::grpc::Status status) { GrpcTradeClient::OnResponse(context, status); });
}

void UniswapGrpcTradeClient::CancelOrder(const OrderExecution& execution) {}

void UniswapGrpcTradeClient::SubmitLiquidity(const LiquiditySpec& liquidity_spec) {
  switch (liquidity_spec.side) {
    case coin::proto::LiquiditySide::PROVIDE:
      ProvideLiquidity(liquidity_spec);
      break;
    case coin::proto::LiquiditySide::WITHDRAW:
      WithdrawLiquidity(liquidity_spec);
      break;
    default:
      CHECK(false) << "unknown liquidity side: " << liquidity_spec.side;
      break;
  }
}

void UniswapGrpcTradeClient::QueryAccountBalance(const std::set<std::string>& currencies) {
  using Response = ::coin2::service::QueryAccountBalanceResponseProto;
  using Request = ::coin2::service::QueryAccountBalanceRequestProto;

  auto context = GetGrpcContextWithTimeout(60);

  auto request = std::make_shared<Request>();
  auto response = std::make_shared<Response>();

  request->set_blockchain_name(blockchain_name_);
  request->set_mea(mea_.String());
  request->set_wallet_address(auth_->GetWalletAddress());
  for (const auto& currency : currencies) {
    request->add_currencies(currency);
  }

  context->request = request;
  context->response = response;

  context->query_type = GRPC_QUERY_ACCOUNT_BALANCE;

  stub_->async()->query_account_balance(
      &context->context,
      request.get(),
      response.get(),
      [context, this](::grpc::Status status) { GrpcTradeClient::OnResponse(context, status); });
}

void UniswapGrpcTradeClient::QueryNftTokenInfo(const std::string& symbol) {
  using Response = ::coin2::service::QueryNftTokenInfoResponseProto;
  using Request = ::coin2::service::QueryNftTokenInfoRequestProto;

  auto context = GetGrpcContextWithTimeout(60);

  auto request = std::make_shared<Request>();
  auto response = std::make_shared<Response>();

  request->set_blockchain_name(blockchain_name_);
  request->set_mea(mea_.String());
  request->set_wallet_address(auth_->GetWalletAddress());
  request->set_symbol(symbol);

  context->request = request;
  context->response = response;

  context->query_type = GRPC_QUERY_NFT_TOKEN_INFO;

  stub_->async()->query_nft_token_info(
      &context->context,
      request.get(),
      response.get(),
      [context, this](::grpc::Status status) { GrpcTradeClient::OnResponse(context, status); });
}

std::shared_ptr<::coin2::service::QuerySubmitOrderRequestProto>
UniswapGrpcTradeClient::GetPlaceOrderRequest(const OrderSpec& order_spec) {
  using Request = ::coin2::service::QuerySubmitOrderRequestProto;

  auto request = std::make_shared<Request>();

  request->set_blockchain_name(blockchain_name_);
  request->set_mea(mea_.String());
  request->set_wallet_address(auth_->GetWalletAddress());

  request->set_symbol(order_spec.product);

  if (order_spec.order_side == OrderSide::BUY_ORDER) {
    request->set_side("buy");
  } else if (order_spec.order_side == OrderSide::SELL_ORDER) {
    request->set_side("sell");
  } else {
    NOTREACHED() << "Unknown order side: " << static_cast<int>(order_spec.order_side);
  }

  request->set_qty(order_spec.qty);
  if (order_spec.order_type != OrderType::MARKET_ORDER) {
    request->set_price(order_spec.price);
  }

  return request;
}

void UniswapGrpcTradeClient::ProvideLiquidity(const LiquiditySpec& liquidity_spec) {
  using Request = ::coin2::service::QueryProvideLiquidityRequestProto;
  using Response = ::coin2::service::QueryProvideLiquidityResponseProto;

  auto context = GetGrpcContextWithTimeout(60);

  auto request = std::make_shared<Request>();
  auto response = std::make_shared<Response>();

  request->set_blockchain_name(blockchain_name_);
  request->set_mea(mea_.String());
  request->set_wallet_address(auth_->GetWalletAddress());
  request->set_symbol(liquidity_spec.symbol);
  request->set_min_price(liquidity_spec.min_price);
  request->set_max_price(liquidity_spec.max_price);

  context->request = request;
  context->response = response;

  context->query_type = GRPC_PROVIDE_LIQUIDITY;

  stub_->async()->query_provide_liquidity(
      &context->context,
      request.get(),
      response.get(),
      [context, this](::grpc::Status status) { GrpcTradeClient::OnResponse(context, status); });
}

void UniswapGrpcTradeClient::WithdrawLiquidity(const LiquiditySpec& liquidity_spec) {
  using Request = ::coin2::service::QueryWithdrawLiquidityRequestProto;
  using Response = ::coin2::service::QueryWithdrawLiquidityResponseProto;

  auto context = GetGrpcContextWithTimeout(60);

  auto request = std::make_shared<Request>();
  auto response = std::make_shared<Response>();

  request->set_blockchain_name(blockchain_name_);
  request->set_mea(mea_.String());
  request->set_wallet_address(auth_->GetWalletAddress());
  request->set_symbol(liquidity_spec.symbol);

  context->request = request;
  context->response = response;

  context->query_type = GRPC_WITHDRAW_LIQUIDITY;

  stub_->async()->query_withdraw_liquidity(
      &context->context,
      request.get(),
      response.get(),
      [context, this](::grpc::Status status) { GrpcTradeClient::OnResponse(context, status); });
}

auto UniswapGrpcTradeClient::GetGrpcContextWithTimeout(unsigned int timeout)
    -> std::shared_ptr<GrpcContext> {
  auto context = std::make_shared<GrpcContext>();

  // Set timeout for API
  std::chrono::system_clock::time_point deadline =
      std::chrono::system_clock::now() + std::chrono::seconds(timeout);

  context->context.set_deadline(deadline);

  return context;
}

}  // namespace coin2::exchange::uniswap::api_order::grpc
